package com.easy.mq.factory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import com.easy.mq.config.consumer.AbstractConsumerConfig;
import com.easy.mq.event.ObjectListenerMethodInvokeWrapper;
import com.easy.mq.listener.ManagerListener;
import com.easy.mq.listener.MessageListenerContainer;


public abstract class AbstractMQConfigFactory<T> implements ApplicationContextAware, BeanFactoryPostProcessor,
		InitializingBean, FactoryBean<ManagerListener<T>>, BeanPostProcessor {

	private static final Logger logger = LoggerFactory.getLogger(AbstractMQConfigFactory.class);
	
	private ApplicationContext context;

	private ManagerListener<T> managerListener;
	
	
	private List<String> filterClass = Arrays.asList(new String[] {"java.lang.Object","org.springframework","javax.servlet"});
	
	private  Map<String, Object> config;
	
	private  final List<MessageListenerContainer> messageListenerContainerList = new CopyOnWriteArrayList<>();
	
	public AbstractMQConfigFactory(Map<String, Object> config) {
		this.config = config ;
	}
	

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
		if (methods != null) {
			for (Method method : methods) {
				if(isExistClassName(method)) {
					continue;
				}
				Annotation  listener = AnnotationUtils.findAnnotation(method, getAnnotation());
				if(listener != null) {
					initListener(method, bean, listener);
				}
			}
		}
		return bean;
	}
	
	private boolean isExistClassName(Method method) {
		if(method != null) {
			for(String filter : filterClass) {
				if(method.getDeclaringClass().getName().indexOf(filter) > -1) {
					return true;
				}
			}
		}
		return false;
		
	}
	
	private void initListener(Method method,Object bean,Annotation listener) {
		AbstractConsumerConfig  consumerConfig = getConsumerConfig(listener);
		
		Type[] types = method.getGenericParameterTypes();
		if (types != null && types.length > 0) {
			if (types[0] instanceof ParameterizedType) {
				ParameterizedType pType = (ParameterizedType) types[0];
				consumerConfig.setParentType(pType.getRawType().getTypeName());
				consumerConfig.setParamType(typeName(pType, types));
			}
		}

		consumerConfig.setListenerClassName(bean.toString());
		consumerConfig.setTargetMethod(method.getName());
		
		
		if (logger.isInfoEnabled()) {
			logger.info("Message add Annotation Method Listener  class: {},method: {},topic: {}",bean.getClass().getName(),method.getName(),consumerConfig.getTopic());
		}
		ObjectListenerMethodInvokeWrapper<T> wrapper = new ObjectListenerMethodInvokeWrapper<T>();
		wrapper.setManagerListener(managerListener);
		wrapper.setConsumerConfig(consumerConfig);
		wrapper.setListenerClassName(listener.annotationType().getName());
		wrapper.setKey(managerListener.getKey(consumerConfig));
		wrapper.setTargetObject(bean);
		wrapper.setTargetMethod(method.getName());
		wrapper.init();
		
		MessageListenerContainer messageListenerContainer = new AdapterFactory<T>().getListenerContainer(listener, managerListener);
		if(messageListenerContainer != null) {
			messageListenerContainer.registerMessageListener(consumerConfig);
			messageListenerContainerList.add(messageListenerContainer);
		}
		
	}
	
	private String typeName(ParameterizedType pType,Type[] types) {
		if (pType == null || pType.getActualTypeArguments().length <= 0) {
			return null;
		}
		
		final String typeName = (pType.getActualTypeArguments()[0]).getTypeName();
		if(typeName != null && typeName.indexOf("<") <=-1) {
			return typeName;
		
		}
		pType = (ParameterizedType) pType.getActualTypeArguments()[0];
		return typeName(pType, types);
		
	}

	@Override
	public ManagerListener<T> getObject() throws Exception {
		return managerListener;
	}

	@Override
	public Class<?> getObjectType() {
		return ManagerListener.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		managerListener = new ManagerListener<T>();

	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = applicationContext;
	}

	public ManagerListener<T> getManagerConfig() {
		return managerListener;
	}

	public void setManagerConfig(ManagerListener<T> managerConfig) {
		this.managerListener = managerConfig;
	}

	public ApplicationContext getContext() {
		return context;
	}



	public void setContext(ApplicationContext context) {
		this.context = context;
	}


	public Map<String,Object> getConfig() {
		return config;
	}


	public void setConfig(Map<String, Object> config) {
		this.config = config;
	}
	
	public abstract Class<Annotation> getAnnotation();


	public abstract AbstractConsumerConfig getConsumerConfig(Annotation annotation);
}
